﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


namespace Tesla.Input {
    /// <summary>
    /// Represents the state of the keyboard, e.g. which keys are pressed.
    /// </summary>
    public struct KeyboardState {

        private static uint _maskValue = uint.MaxValue;

        private uint _keyState0;
        private uint _keyState1;
        private uint _keyState2;
        private uint _keyState3;
        private uint _keyState4;
        private uint _keyState5;
        private uint _keyState6;
        private uint _keyState7;

        /// <summary>
        /// Query the keystate for the specified key.
        /// </summary>
        /// <param name="key">Specified key</param>
        /// <returns>The keystate for that key</returns>
        public KeyState this[Keys key] {
            get {
                uint num = 0;
                int grouping = ((int) key) >> 5;
                switch(grouping) {
                    case 0:
                        num = _keyState0;
                        break;
                    case 1:
                        num = _keyState1;
                        break;
                    case 2:
                        num = _keyState2;
                        break;
                    case 3:
                        num = _keyState3;
                        break;
                    case 4:
                        num = _keyState4;
                        break;
                    case 5:
                        num = _keyState5;
                        break;
                    case 6:
                        num = _keyState6;
                        break;
                    case 7:
                        num = _keyState7;
                        break;
                }
                uint num2 = ((uint) 1) << (int) key;
                if((num & num2) == 0) {
                    return KeyState.Up;
                } else {
                    return KeyState.Down;
                }
            }
        }

        /// <summary>
        /// Create a new KeyboardState with the specified keys
        /// pressed.
        /// </summary>
        /// <param name="keys">Keys that are pressed</param>
        public KeyboardState(params Keys[] keys) {
            _keyState0 = _keyState1 = _keyState2 = _keyState3 = _keyState4 = _keyState5 = _keyState6 = _keyState7 = 0;
            if(keys != null) {
                for(int i = 0; i < keys.Length; i++) {
                    AddPressedKey(keys[i]);
                }
            }
        }

        /// <summary>
        /// Create a new KeyboardState with the specified key group masks.
        /// </summary>
        /// <param name="group0"></param>
        /// <param name="group1"></param>
        /// <param name="group2"></param>
        /// <param name="group3"></param>
        /// <param name="group4"></param>
        /// <param name="group5"></param>
        /// <param name="group6"></param>
        /// <param name="group7"></param>
        public KeyboardState(uint group0, uint group1, uint group2, uint group3, uint group4, uint group5, uint group6, uint group7) {
            _keyState0 = group0;
            _keyState1 = group1;
            _keyState2 = group2;
            _keyState3 = group3;
            _keyState4 = group4;
            _keyState5 = group5;
            _keyState6 = group6;
            _keyState7 = group7;
        }

        /// <summary>
        /// Query if the specified key is down and thus
        /// pressed.
        /// </summary>
        /// <param name="key">Key to query</param>
        /// <returns>If the key is pressed</returns>
        public bool IsKeyDown(Keys key) {
            return this[key] == KeyState.Down;
        }

        /// <summary>
        /// Query if the specified key is up and thus
        /// not pressed.
        /// </summary>
        /// <param name="key">Key to query</param>
        /// <returns>If the key is not pressed</returns>
        public bool IsKeyUp(Keys key) {
            return this[key] == KeyState.Up;
        }

        private void AddPressedKey(Keys key) {
            //Get the bit place in the state for the key
            uint place = ((uint) 1) << (int)key;
            //Get the state grouping for the key
            int grouping = (int)key >> 5;
            //Add the key to the proper state
            switch(grouping) {
                case 0:
                    _keyState0 |= place & _maskValue;
                    return;
                case 1:
                    _keyState1 |= place & _maskValue;
                    return;
                case 2:
                    _keyState2 |= place & _maskValue;
                    return;
                case 3:
                    _keyState3 |= place & _maskValue;
                    return;
                case 4:
                    _keyState4 |= place & _maskValue;
                    return;
                case 5:
                    _keyState5 |= place & _maskValue;
                    return;
                case 6:
                    _keyState6 |= place & _maskValue;
                    return;
                case 7:
                    _keyState7 |= place & _maskValue;
                    return;
            }
        }

        private void RemovePressedKey(Keys key) {
            //Get the bit place for the key
            uint place = ((uint) 1) << (int)key;
            //Get the state grouping for the key
            int grouping = (int)key >> 5;
            //Remove the key from the proper state
            switch(grouping) {
                case 0:
                    _keyState0 &= ~(place & _maskValue);
                    return;
                case 1:
                    _keyState1 &= ~(place & _maskValue);
                    return;
                case 2:
                    _keyState2 &= ~(place & _maskValue);
                    return;
                case 3:
                    _keyState3 &= ~(place & _maskValue);
                    return;
                case 4:
                    _keyState4 &= ~(place & _maskValue);
                    return;
                case 5:
                    _keyState5 &= ~(place & _maskValue);
                    return;
                case 6:
                    _keyState6 &= ~(place & _maskValue);
                    return;
                case 7:
                    _keyState7 &= ~(place & _maskValue);
                    return;
            }
        }

        /// <summary>
        /// Get an array of pressed keys.
        /// </summary>
        /// <returns>An array of keys that are pressed</returns>
        public Keys[] GetPressedKeys() {
            //Bit of a hack, find the number of pressed keys so we
            //can create the properly sized key array
            int index = 0;
            CheckPressedKeys(_keyState0, 0, null, ref index);
            CheckPressedKeys(_keyState1, 0, null, ref index);
            CheckPressedKeys(_keyState2, 0, null, ref index);
            CheckPressedKeys(_keyState3, 0, null, ref index);
            CheckPressedKeys(_keyState4, 0, null, ref index);
            CheckPressedKeys(_keyState5, 0, null, ref index);
            CheckPressedKeys(_keyState6, 0, null, ref index);
            CheckPressedKeys(_keyState7, 0, null, ref index);
            Keys[] pressedKeys = new Keys[index];
            //If we have pressed keys, decode the stateNames and get the proper
            //key codes
            if(index > 0) {
                int index2 = 0;
                CheckPressedKeys(_keyState0, 0, pressedKeys, ref index2);
                CheckPressedKeys(_keyState1, 1, pressedKeys, ref index2);
                CheckPressedKeys(_keyState2, 2, pressedKeys, ref index2);
                CheckPressedKeys(_keyState3, 3, pressedKeys, ref index2);
                CheckPressedKeys(_keyState4, 4, pressedKeys, ref index2);
                CheckPressedKeys(_keyState5, 5, pressedKeys, ref index2);
                CheckPressedKeys(_keyState6, 6, pressedKeys, ref index2);
                CheckPressedKeys(_keyState7, 7, pressedKeys, ref index2);
            }
            return pressedKeys;
        }

        private void CheckPressedKeys(uint keyState, int grouping, Keys[] pressedKeys, ref int index) {
            //If the state is zero, we have nothing pressed
            if(keyState != 0) {
                //For each key, check to see if its pressed
                for(int i = 0; i < 32; i++) {
                    //If its pressed, get the keycode
                    if((keyState & (((uint) 1) << 1)) != 0) {
                        if(pressedKeys != null) {
                            pressedKeys[index] = (Keys) ((grouping * 32) + i);
                        }
                        index++;
                    }
                }
            }
        }

        /// <summary>
        /// Get the hash code for the KeyboardState
        /// </summary>
        /// <returns>State hashcode</returns>
        public override int GetHashCode() {
            return (int) ((_keyState0 * 1) + (_keyState1 * 2) + (_keyState2 * 3)
                + (_keyState3 * 4) + (_keyState4 * 5) + (_keyState5 * 6)
                + (_keyState6 * 7) + (_keyState7 * 8));
        }

        /// <summary>
        /// Tests if the current instance is equal to the specified object.
        /// </summary>
        /// <param name="obj">Object to test</param>
        /// <returns>True if equal</returns>
        public override bool Equals(object obj) {
            if(obj is KeyboardState) {
                return this == (KeyboardState) obj;
            }
            return false;
        }

        /// <summary>
        /// Tests if two KeyboardStates are equal
        /// </summary>
        /// <param name="a">First KeyboardState</param>
        /// <param name="b">Second KeyboardState</param>
        /// <returns>True if equal</returns>
        public static bool operator==(KeyboardState a, KeyboardState b) {
            return (a._keyState0 == b._keyState0) && (a._keyState1 == b._keyState1)
                && (a._keyState2 == b._keyState2) && (a._keyState3 == b._keyState3)
                && (a._keyState4 == b._keyState4) && (a._keyState5 == b._keyState5)
                && (a._keyState6 == b._keyState6) && (a._keyState7 == b._keyState7);
        }

        /// <summary>
        /// Tests if two KeyboardStates are not equal
        /// </summary>
        /// <param name="a">First KeyboardState</param>
        /// <param name="b">Second KeyboardState</param>
        /// <returns>True if not equal</returns>
        public static bool operator!=(KeyboardState a, KeyboardState b) {
            return !(a == b);
        }

    }
}